/*
 * Copyright (C) 2008-2012  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

/*
 * Implementation
 */
ebool_t
erat_set_si(erat_t u, eword_t n, eword_t d)
{
    erat_num(u) = n;
    erat_den(u) = d;

    return (erat_canonicalize(u));
}

int
erat_cmp(erat_t u, erat_t v)
{
    eword_t		n;
    eword_t		d;

    /* if signs differ */
    if (erat_num(u) < 0) {
	if (erat_num(v) >= 0)
	    return (-1);
    }
    else if (erat_num(v) < 0)
	return (1);

    /* if same denominator */
    if (erat_den(u) == erat_den(v)) {
	if (erat_num(u) < erat_num(v))
	    return (-1);
	if (erat_num(u) > erat_num(v))
	    return (1);
	return (0);
    }

    /* if same numerator */
    if (erat_num(u) == erat_num(v)) {
	if (erat_den(u) > erat_den(v))
	    return (-1);
	if (erat_den(u) < erat_den(v))
	    return (1);
	return (0);
    }

    n = ew_mul_w_w(erat_num(u), erat_den(v));
    d = ew_mul_w_w(erat_num(v), erat_den(u));
    if (unlikely(n == MININT || d == MININT)) {
	mpq_set_si(thr_qr, erat_num(u), erat_den(u));
	mpq_set_si(thr_qi, erat_num(v), erat_den(v));

	return (mpq_cmp(thr_qr, thr_qi));
    }

    if (n < 0) {
	n = -n;
	d = -d;
    }

    return (n < d ? -1 : n > d ? 1 : 0);
}

ebool_t
erat_add(erat_t u, erat_t v, erat_t w)
{
    eword_t		n;
    eword_t		d;
    rat_t		r;

    n = ew_mul_w_w(erat_num(v), erat_den(w));
    d = ew_mul_w_w(erat_num(w), erat_den(v));
    rat_num(r) = n + d;
    rat_den(r) = ew_mul_w_w(erat_den(v), erat_den(w));
    if (unlikely(rat_den(r) == MININT || n == MININT || d == MININT ||
		 eadd_over_p(rat_num(r), n, d)))
	return (false);

    if (!erat_canonicalize(&r))
	return (false);

    erat_num(u) = rat_num(r);
    erat_den(u) = rat_den(r);

    return (true);
}

ebool_t
erat_sub(erat_t u, erat_t v, erat_t w)
{
    eword_t		n;
    eword_t		d;
    rat_t		r;

    n = ew_mul_w_w(erat_num(v), erat_den(w));
    d = ew_mul_w_w(erat_num(w), erat_den(v));
    rat_num(r) = n - d;
    rat_den(r) = ew_mul_w_w(erat_den(v), erat_den(w));
    if (unlikely(rat_den(r) == MININT || n == MININT || d == MININT ||
		 esub_over_p(rat_num(r), n, d)))
	return (false);

    if (!erat_canonicalize(&r))
	return (false);

    erat_num(u) = rat_num(r);
    erat_den(u) = rat_den(r);

    return (true);
}

ebool_t
erat_mul(erat_t u, erat_t v, erat_t w)
{
    rat_t		r;

    rat_num(r) = ew_mul_w_w(erat_num(v), erat_num(w));
    rat_den(r) = ew_mul_w_w(erat_den(v), erat_den(w));

    if (unlikely(rat_num(r) == MININT || rat_den(r) == MININT))
	return (false);

    if (!erat_canonicalize(&r))
	return (false);

    erat_num(u) = rat_num(r);
    erat_den(u) = rat_den(r);

    return (true);
}

ebool_t
erat_div(erat_t u, erat_t v, erat_t w)
{
    rat_t		r;

    rat_num(r) = ew_mul_w_w(erat_num(v), erat_den(w));
    rat_den(r) = ew_mul_w_w(erat_den(v), erat_num(w));

    if (unlikely(rat_num(r) == MININT || rat_den(r) == MININT))
	return (false);

    if (!erat_canonicalize(&r))
	return (false);

    erat_num(u) = rat_num(r);
    erat_den(u) = rat_den(r);

    return (true);
}

ebool_t
erat_canonicalize(erat_t rat)
{
    eword_t		n;
    eword_t		d;
    eword_t		m;

    n = erat_num(rat);
    d = erat_den(rat);

    if (unlikely(d == 0))
	ethread_kill(SIGFPE);

    if (unlikely(n == MININT || d == MININT))
	return (false);

    if (d == 1)
	return (true);
    if (n == 0) {
	erat_den(rat) = 1;
	return (true);
    }

    if (n < 0)
	n = -n;
    for (;;) {
	if ((m = d % n) == 0)
	    break;
	d = n;
	n = m;
    }
    if (d != 1) {
	erat_num(rat) /= n;
	erat_den(rat) /= n;
    }

    if (erat_den(rat) < 0) {
	erat_num(rat) = -erat_num(rat);
	erat_den(rat) = -erat_den(rat);
    }

    return (true);
}
